﻿# （1）我是quant林，早年供职于上海的量化私募，后于2017年推出的免费Python期货CTP接口开源框架，采用GPLV3开源协议，简单易学，
# 适合学习入门和拓展开发，就在1.0版发布后不久，我的vnapi 1.0（当时不叫vnapi）就遭受了非法金融经营者用1.2万字连载诽谤文的诋毁攻击，
# 他们甚至在诽谤文下方还放上他们自己的涉及非法金融开源软件的广告。
# 从那次诋毁到现在2.10版的重新推出已过了快7年，人生又有几个7年？
# 当初我作为技术创业者从未遇到类似的情况，不知如何应对，2017年7月我离开上海去了重庆，在重庆
# 写出了virtualapi仿真回测（盛立版、CTP版、通联沪深A股Level2版）、vntrader6框架雏形、kucps专业投资软件的代码，
# 每天开发工作完毕，就去万州的小巷点一份小火锅和啤酒，这是我最难忘的回忆，2019年我又回到了上海，
# 在最近刀郎事件发生后，我真怀疑我离开上海的想法和刀郎的想法是一样的。
# 由于非法金融具有伪装性和渗透性，前几年利用开源开源旗号不断侵蚀我国合规金融市场，某币圈网红公开宣传非法金融，
# 其带有非法金融交易功能的软件甚至短暂的上了某期货公司官网，后他们又于2021年11月被上海证监局调查。
# 在过去7年中，我在打击非法金融的战线上贡献了自己的力量，付出了大量时间和精力的代价，说不清楚这样做是为了自救，
# 还是别的原因，但我忠心的希望金融从业者们牢记合规底线，勿给非法金融可乘之机。
# 为记住那段历史，本次发布的vnapi2.10 期货CTP接口行情库底层代码，正是源于2017年4月的1.0开源版本的稍作整理，已升级到最新的CTP版本，
# 1.0版就是被诋毁造谣的那个版本，vnapi改名前的1.0 版本曾被北京大学出版社《python3.x全栈开发》收录并重点介绍，
# 现在vnapi2.0作为一款Python CTP接口的框架，和当初1.0一样，是基于Ctypes技术，简单易学，主要目的是为了为开发者
# 提供一个通俗易懂的python后端开源框架，并非为了提供一款面面俱到的商业产品，适合入门学习和在此基础上继续拓展开发。
# 我还采用PyQt技术开发为前端开发的 vntrader6客户端（https://www.vnpy.cn）， 基于我开发的vnapi的另一个回调版本，
# 面向的是专业机构客户，VNTrader系列可以说在vnapi 1.0后端框架技术上迭代产生的，
# 当初的1.0，和今天发布的2.0，以及VNTrader、Virtualapi产品均属于我和上海量贝软件的原创产品，
# 我于2014年还获得基于AR增强现实技术的国家发明专利一项。
# 我另有正在申请3项量化交易发明专利，这些技术已授予用于上海量贝作为技术积累可用于商业技术开发。
# 也希望谣言发布者们知晓，靠谣言诋毁一个执迷于技术的程序员和技术创业者是不可能成功的。
# 承载着7年的维权经历与梦想，引来了vnapi2.10版本的发布，这个产品定位是作抛砖引玉之用，适合用户学习Python量化和扩展功能，
# 而非堆砌功能。
# （2）vnapi 遵循 开源协议GPL v3
# 简单得说：对基于GPL v3协议的源代码，若个人或机构仅仅是自己使用，则可以闭源。
# 若基于该开源代码，开发出程序或衍生产品用于商业行为则也需开源。
# 官方网站：http://www.vnpy.cn

# -*- coding=utf-8 -*-
# 导入CTP行情库
from vnapictpmd import *
# 导入CTP交易库
from vnapictptd import *
from vnapictptdType import *
import vnapictptdType
# 导入多进程库
# import multiprocessing
import multiprocessing
# 导入时间库
import time, datetime
import threading

md = vnapictpmd()  # 行情接口类赋值给变量
td = vnapictptd()  # 交易接口类赋值给变量
# 限制交易时间
today = datetime.date.today()
gStart1 = datetime.datetime(today.year, today.month, today.day, 9, 30, 0)  # 开盘时间1
gEnd1 = datetime.datetime(today.year, today.month, today.day, 11, 30, 0)  # 收盘时间1
gStart2 = datetime.datetime(today.year, today.month, today.day, 13, 0, 0)  # 开盘时间2
gEnd2 = datetime.datetime(today.year, today.month, today.day, 15, 0, 0)  # 收盘时间2
gExitTime = datetime.datetime(today.year, today.month, today.day, 15, 1, 0)  # 退出运行的时间


def IsStockTrade():
    now = datetime.datetime.now()
    if ((gStart1 < now and now < gEnd1) or
            (gStart2 < now and now < gEnd2)):
        return True
    else:
        return False


def StrategyProcess(msg, price):  # (Instrument,lastprice):
    # print u"********************** [%s]lastprice:%0.02f\n"%( Instrument,lastprice)
    print("StrategyProcess Instrument:", msg)
    # print(name,price)
    # print u"******[%s]\n"%msg
    return "done" + msg


# -------------------------------------------------以下是MD回调--------------------------------------------------
# ------------------------------------------MD回调函数、相关变量开始----------------------------------------------
num = 0

# 回调类型
MD_EMPTY = 8000  # 无消息
MD_LOGIN_SCUESS = 8001  # 登录成功
MD_LOGIN_DENIED = 8002  # 登录被拒绝
# MD_LOGIN_ERRORPASSWORD     = 8003 #密码错误(行情没有密码错误)
MD_LOGINOUT_SCUESS = 8004  # 登出成功
MD_NETCONNECT_SCUESS = 8005  # 连接成功
MD_NETCONNECT_BREAK = 8006  # 断开连接
MD_NETCONNECT_FAILER = 8007  # 连接失败
MD_SUBCRIBE_SCUESS = 8008  # 订阅成功
MD_UNSUBCRIBE_SCUESS = 8009  # 取消订阅成功
MD_NEWTICK = 8010  # 新Tick到来
MD_SYSTEM_ERROR = 8011  # 错误应答
MD_QRY_FORQUOTE = 8012  # 询价通知


def MD_OnEmptyCmd():
    # 回调指令缓冲区已为空（因为短时间获得多个指令，时间间隔态度，在下面的for i in range(md.GetUnGetCmdSize()):循环执行了多次已经完成了）
    print("---------------MD_OnEmptyCmd---------------")


def MD_OnUserLogin():
    # 登录成功
    print("---------------MD_OnUserLogin---------------")
    data = ctypes.cast(md.GetCmdContent_LoginScuess(), ctypes.POINTER(VNCThostFtdcRspUserLoginField))
    print("TradingDay %s" % (str(data[0].TradingDay)))  # 交易日
    print("LoginTime %s" % (str(data[0].LoginTime)))  # 登录成功时间
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("UserID %s" % (str(data[0].UserID)))  # 用户代码
    print("SystemName %s" % (str(data[0].SystemName)))  # 交易系统名称
    print("FrontID %s" % (str(data[0].FrontID)))  # 前置编号
    print("SessionID %s" % (str(data[0].SessionID)))  # 会话编号
    print("MaxOrderRef %s" % (str(data[0].MaxOrderRef)))  # 最大报单引用
    print("SHFETime %s" % (str(data[0].SHFETime)))  # 上期所时间
    print("DCETime %s" % (str(data[0].DCETime)))  # 大商所时间
    print("CZCETime %s" % (str(data[0].CZCETime)))  # 郑商所时间
    print("FFEXTime %s" % (str(data[0].FFEXTime)))  # 中金所时间
    print("INETime %s" % (str(data[0].INETime)))  # 能源中心时间


def MD_OnUserLoginDenied():
    # 登录被拒绝
    print("---------------MD_OnUserLoginDenied---------------")


def MD_OnUserLogout():
    # 登出成功
    print("---------------MD_OnUserLogout---------------")
    data = ctypes.cast(md.GetCmdContent_LoginOut(), POINTER(VNCThostFtdcRspUserLoginField))
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 期货公司brokeid
    print("UserID %s" % (str(data[0].UserID)))  # 账户


def MD_OnFrontConnected():
    # 与行情服务器连接成功
    # 当客户端与交易后台建立起通信连接时（还未登录前），该方法被调用。
    print("---------------MD_OnFrontConnected---------------")


def MD_OnFrontDisconnected():
    # 与行情服务器断开连接
    # 当客户端与交易后台通信连接断开时，该方法被调用。当发生这个情况后，API会自动重新连接，客户端可不做处理。
    print("---------------MD_OnFrontDisconnected---------------")


def MD_OnFrontConnectedFailer():
    # 连接失败
    print("---------------MD_OnFrontConnectedFailer---------------")


def MD_OnSubMarketData():
    # 订阅成功
    print("---------------MD_OnSubMarketData---------------")
    data =ctypes.cast(md.GetCmdContent_SubMarketData(), POINTER(VNInstrument))
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码


def MD_OnUnSubMarketData():
    # 取消订阅行情成功
    print("---------------MD_OnUnSubMarketData---------------")
    data = ctypes.cast(md.GetCmdContent_UnSubMarketData(), POINTER(VNInstrument))
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码


def MD_OnTick():
    # 新的一笔Tick数据驱动
    # print "---------------MD_OnTick---------------"
    global num
    num = num + 1
    # 取得新TICK的合约代码
    Instrument = md.GetCmdContent_Tick()
    # print "Instrument %s"%Instrument
    # 打印该合约数据, 可增加交易策略逻辑计算，计算进程放入其它线程或进程中，以免耗时计算阻塞行情接收和其它回调
    print("(%d)%s %s [%0.02f][%0.00f]" % (
        num, Instrument, md.InstrumentID(Instrument), md.LastPrice(Instrument), md.Volume(Instrument)))


def MD_OnError():
    # 错误信息回报
    print("---------------MD_OnRspError---------------")
    data = ctypes.cast(md.GetCmdContent_Error(), POINTER(VNCThostFtdcRspInfoField))
    print("ErrorID %s" % (str(data[0].ErrorID)))  # 错误代码
    print("ErrorMsg %s" % (str(data[0].ErrorMsg)))  # 错误信息


def MD_OnForQuote():
    # 询价通知
    print("---------------MD_OnForQuote---------------")
    data = ctypes.cast(md.GetCmdContent_Forquote(), POINTER(VNCThostFtdcForQuoteRspField))
    print("TradingDay %s" % (str(data[0].TradingDay)))  # 交易日
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码
    print("ForQuoteSysID %s" % (str(data[0].ForQuoteSysID)))  # 询价编号
    print("ForQuoteTime %s" % (str(data[0].ForQuoteTime)))  # 询价时间
    print("ActionDay %s" % (str(data[0].ActionDay)))  # 业务日期
    print("ExchangeID %s" % (str(data[0].ExchangeID)))  # 交易所代码


mddict = {
    MD_EMPTY: MD_OnEmptyCmd,
    MD_LOGIN_SCUESS: MD_OnUserLogin,
    MD_LOGIN_DENIED: MD_OnUserLoginDenied,
    MD_LOGINOUT_SCUESS: MD_OnUserLogout,
    MD_NETCONNECT_SCUESS: MD_OnFrontConnected,
    MD_NETCONNECT_BREAK: MD_OnFrontDisconnected,
    MD_NETCONNECT_FAILER: MD_OnFrontConnectedFailer,
    MD_SUBCRIBE_SCUESS: MD_OnSubMarketData,
    MD_UNSUBCRIBE_SCUESS: MD_OnUnSubMarketData,
    MD_NEWTICK: MD_OnTick,
    MD_SYSTEM_ERROR: MD_OnError,
    MD_QRY_FORQUOTE: MD_OnForQuote
}


# ------------------------------------------MD回调函数、相关变量结束----------------------------------------------

# main()为程序入口函数，所有的行情、交易订阅、指标调用、下单的逻辑均写在此函数内执行


def MDThread(func):
    # 除了从配置文件读取注册服务器地址外，还可用RegisterFront添加注册多个行情服务器IP地址。稍后给出不读配置文件，只使用RegisterFront注册行情服务器地址的例子
    if False:
        # 已从配置文件读取3个服务器地址，这里可以不添加注册了
        md.RegisterFront("tcp://180.168.146.187:10031")  # 添加注册行情服务器地址1
        md.RegisterFront("tcp://180.168.146.187:10031")  # 添加注册行情服务器地址2
        md.RegisterFront("tcp://180.168.146.187:10031")  # 添加注册行情服务器地址3

    # os.system("QucikLib")
    # 从配置文件Instrument.ini  读取订阅的合约，每行写一个要订阅行情的合约，用调用ReadInstrument()的方式就无需通过调用Subcribe系列函数方式来订阅合约了，编译成exe后，也方便通过更改配置文件来更改合约

    retLogin = td.ReqUserLogin()  # 调用交易接口元素，通过 “ 接口变量.元素（接口类内部定义的方法或变量） ” 形式调用
    # Login()，不需要参数，Login读取QuickLibTD.ini的配置信息，并登录
    # 返回0， 表示登录成功，
    # 返回1， FutureTDAccount.ini错误
    # 返回2， 登录超时
    print('login: ', retLogin)
    if retLogin == 0:
        print('登陆交易成功')
    else:
        print('登陆交易失败')

    # 设置拒绝接收行情服务器数据的时间，有时候（特别是模拟盘）在早晨6-8点会发送前一天的行情数据，若不拒收的话，会导致历史数据错误，本方法最多可以设置4个时间段进行拒收数据
    # md.SetRejectdataTime(0.0400, 0.0840, 0.1530, 0.2030, NULL, NULL, NULL, NULL);

    # 读取合约订阅的配置文件

    # 订阅品种zn1610，接收Tick数据,不根据Tick生成其他周期价格数据,但可根据AddPeriod函数添加周期价格数据的设置
    md.SubscribeMarketData('rb2312')
    md.SubscribeMarketData('zn2312')
    md.SubscribeMarketData('ag2403')
    md.SubscribeMarketData('au2403')

    # 订阅合约时，请注意合约的大小写，中金所和郑州交易所是大写，上海和大连期货交易所是小写的
    # ReadInstrumentIni(True)调用订阅函数Subcribe，可以用AddPeriod函数添加周期参数
    # 尽量避免使用不到的周期参数，可以节省内存和CPU占用
    # 周期参数：
    # VN_M1    1分钟周期

    # 给au1612添加根据tick生成的周期，尽量避免添加不适用的周期数据，可以降低CPU和内存占用
    # md.AddPeriod('zn2312', VN_M10)  # 添加M3周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('ag1612',VN_M10)  #添加M10周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('ag1612',VN_M5)   #添加M5周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）
    # md.AddPeriod('zn1610',VN_ALL)  #添加所有M1、M3、M5、M10、M15、M30、M60、M120、D1周期（未在Subcribe、Subcribe1~Subcribe8函数中指定的周期，可以在本函数补充该品种周期，可多次调用函数设置同时保存多个周期）

    # 保存Tick数据,参数1表示简单模式
    # md.SaveTick(2)

    print('number:', md.InstrumentNum)
    # main()函数内的以上部分代码只执行一次，以下while(1)循环内的代码，会一直循环执行，可在这个循环内需增加策略判断，达到下单条件即可下单

    if False:
        # ***************本例暂时取消线程池部分代码，可考虑采用多线、多进程、MPI等方式实现****************
        # 进程数量，应该不大于CPU逻辑核心数
        ProcessNum = multiprocessing.cpu_count()
        print("取本机CPU逻辑核心数%d" % ProcessNum)
        # 初始化线程池，只建立一次进程池，预设进程池的进程最大数量为ProcessNum
        pool = multiprocessing.Pool(processes=ProcessNum)
        # ********************************************************************************************

    # 字典保存各个合约tick计数，用于判断多少次tick计算1次策略，默认为0，从0开始计数
    perdealdict = {"zn1701": 0, "cu1701": 0, "rb2403": 0}
    while (1):  # 死循环
        print(u"Wait for a New Cmd(MD)\n");
        # 判断是否有新Tick数据，while循环不需要Sleep,当没有新Tick时，会处在阻塞状态
        mddict[md.OnCmd()]()
        print(u"Get A New cmd(MD)\n");
        """
        print(u"Wait for a New Tick\n");
        if md.OnTick():  #判断是否有新Tick数据，while循环不需要Sleep,当没有新Tick时，会处在阻塞状态
            print(u"A New Tick\n");
            for i in range(md.GetUnGetTickSize()):
                name=md.GetTickInstrument() #取新TICK合约名称
                if name=="None":
                    #如何获得合约名None，则跳出本次循环。
                    continue;          
                #打印该合约数据 
                print(name,md.InstrumentID(name), md.LastPrice(name)) 
                #*****************本例暂时取消线程池部分代码，可考虑采用多线、多进程、MPI等方式实现*******************
                if False:
                    perdealdict[name]=perdealdict[name]+1 
                    #合约name的tick计数+1
                    if perdealdict[name]>5: #每5次Tick计算一次策略，具体根据策略需要和CPU负荷确定多少次Tick计算一次策略
                        perdealdict[name]=0 #置0后下次从0开始计数
                        #多进程，进程池
                        for tid in xrange(ProcessNum):
                            #msg = "hello %d" %(i)
                            print u"将数据发送给进程池中的一个线程进行策略计算:%s\n"%name
                            #将数据发送给进程池中的一个线程进行策略计算
                            #pool.apply_async(StrategyProcess, (md.InstrumentID(name),))   #维持执行的进程总数为processes，当一个进程执行完毕后会添加新的进程进去
                            #msg = "@@@@@hello: %s\n" %(name)
                            pool.apply_async(StrategyProcess, (name, md.LastPrice(name) ))   #维持执行的进程总数为processes，当一个进程执行完毕后会添加新的进程进去     
            #************************************************************************************************
        """
        # result = pool.apply_async(StrategyProcess, (i,))
        # pool.apply_async(StrategyProcess, (msg, ))   #维持执行的进程总数为processes，当一个进程执行完毕后会添加新的进程进去
        # time.sleep(0.1)  #系统休眠0.1秒
        # 判断当前时间是否在交易时间内，如果在返回真，则开始执行
        # if (IsStockTrade()):

        # if True:
        # print md.LastPrice('zn1610')     #打印该品种在行情接口的变量LastPrice (最新价)
        # print md.BidPrice1('zn1610')     #打印该品种在行情接口的变量BidPrice

        #                       0是当前周期
        # 合约  周期类型  价格类型 多少日前 周期数

        # 打印显示 该品种的1分钟 收盘价
        # print u'1分钟周期,上1个周期的收盘价:'
        #                     品种   周期    价格类型  1天前的数据
        # print u'%f'%md.GetPeriodData('zn1610',VN_M1,VN_CLOSE,0)

        # for i in range(0, 5):
        # print u'%f'%md.GetPeriodData('zn1610',VN_M1,VN_CLOSE,i)

        '''           
        print u'最近5个1分钟周期值'
        print u'-----------begin--------------'
        #打印该品种显示1分钟周期最近5个周期的收盘价格，若价格>0表示已接收到数据，否则还未接收到足够的Tick生成周期K线的数据
        for i in range(0, 3):
               tempclose=md.GetPeriodData('ag1612',VN_M1,VN_CLOSE,i)
               if tempclose>0:
                  print tempclose
        print u'-----------end----------------'

     
        #md.BeginStrategyID(1)
        #合约zn1607的1分钟收盘价，5周期上穿20周期，则下多单
        if md.CrossUp('zn1610',VN_M1,VN_CLOSE,5,20):
              #                                品种代码    多空方向    开仓还是平仓  市价或现价   价格  下单数量
              #下单函数原型 InsertOrder(self, instrumentID, direction,  offsetFlag, priceType,  price,   num):              
             OrderRef = td.InsertOrder('zn1610', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice, md.LastPrice('zn1610')+10, 1)
             #交易记录写日志文件
             #md.LogFile(u'traderecord.csv',u'交易下单  zn1610  VN_D_Buy')
             time.sleep(1)
             #撤销所有未成交的委托单
             ret = td.DeleteOrder('zn1610', OrderRef) 
             
             #对该单号成交的品种设置动态止损，AddStopMonitor未演示，等待封装完成
                       #品种    #单号  #止损方式(动态止损VN_Dynamic,固定价格止损VN_Static ) #止损阈值百分比 3表示离上一次最高点或最低点反向绝对值为3%止损             
             md.AddStopMonitor('zn1610', OrderRef,VN_Dynamic,3)   
             
        #合约zn1607的1分钟收盘价，5周期上穿20周期，则下空单
        if md.CrossDown('zn1610',VN_M1,VN_CLOSE,5,20):
              #                                品种代码    多空方向    开仓还是平仓  市价或现价   价格  下单数量
              #下单函数原型 InsertOrder(self, instrumentID, direction,  offsetFlag, priceType,  price,   num):              
             OrderRef2 = td.InsertOrder('zn1610', VN_D_Sell, VN_OF_Open, VN_OPT_LimitPrice, md.LastPrice('zn1610')-10, 1)    
             #交易记录写日志文件
             #md.LogFile(u'traderecord.csv',u'交易下单  zn1610  VN_D_Sell')
             time.sleep(1)
             #撤销所有未成交的委托单
             ret = td.DeleteOrder('zn1610', OrderRef)
          
        '''
        # md.EndStrategyID(1)
        # while(datetime.datetime.now() < gExitTime):
        # 时间大于退出时间，退出程序。实际操盘中，应考虑夜盘交易时间的影响
        # print (u'未到开盘时间')
        # time.sleep(60)


# -------------------------------------------------以下是TD回调--------------------------------------------------

# ------------------------------------------TD回调函数、相关变量结束----------------------------------------------
# 回调类型
TD_EMPTY = 8000  # 登录成功
TD_LOGIN_SCUESS = 8001  # 登录成功
TD_LOGIN_DENIED = 8002  # 登录被拒绝
TD_LOGIN_ERRORPASSWORD = 8003  # 密码错误
TD_LOGINOUT_SCUESS = 8004  # 登出成功
TD_NETCONNECT_SCUESS = 8005  # 连接成功
TD_NETCONNECT_BREAK = 8006  # 断开连接
TD_NETCONNECT_FAILER = 8007  # 连接失败
# TD_SUBCRIBE_SCUESS         = 8008 #订阅成功
# TD_UNSUBCRIBE_SCUESS       = 8009 #取消订阅成功
# TD_NEWTICK                 = 8010 #新Tick到来
TD_ERROR = 8011  # 错误应答
TD_ORDER_INFO = 8012  # 订单回报
TD_SETTLEMENTINFOCONFIRM = 8013  # 结算单确认回调
TD_MAXORDERVOLUME = 8014  # 最大允许报单数量回调


def TD_OnEmptyCmd():
    # 回调指令缓冲区已为空（因为短时间获得多个指令，时间间隔态度，在下面的for i in range(md.GetUnGetCmdSize()):循环执行了多次已经完成了）
    print("---------------TD_OnEmptyCmd---------------")


def TD_OnUserLogin():
    # 登录成功
    print("---------------TD_OnUserLogin---------------")
    data = cast(td.GetCmdContent_LoginScuess(), POINTER(VNCThostFtdcRspUserLoginField))
    print("TradingDay %s" % (str(data[0].TradingDay)))  # 交易日
    print("LoginTime %s" % (str(data[0].LoginTime)))  # 登录成功时间
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("UserID %s" % (str(data[0].UserID)))  # 用户代码
    print("SystemName %s" % (str(data[0].SystemName)))  # 交易系统名称
    print("FrontID %s" % (str(data[0].FrontID)))  # 前置编号
    print("SessionID %s" % (str(data[0].SessionID)))  # 会话编号
    print("MaxOrderRef %s" % (str(data[0].MaxOrderRef)))  # 最大报单引用
    print("SHFETime %s" % (str(data[0].SHFETime)))  # 上期所时间
    print("DCETime %s" % (str(data[0].DCETime)))  # 大商所时间
    print("CZCETime %s" % (str(data[0].CZCETime)))  # 郑商所时间
    print("FFEXTime %s" % (str(data[0].FFEXTime)))  # 中金所时间
    print("INETime %s" % (str(data[0].INETime)))  # 能源中心时间


def TD_OnUserLoginDenied():
    # 登录被拒绝
    print("---------------TD_OnUserLoginDenied---------------")


def TD_OnUserLoginErrPassword():
    # 登录密码错误
    print("---------------TD_OnUserLoginErrPassword---------------")


def TD_OnUserLogout():
    # 登出成功
    print("---------------TD_OnUserLogout---------------")


def TD_OnFrontConnected():
    # 连接成功
    print("---------------TD_OnFrontConnected---------------")


def TD_OnFrontDisconnected():
    # 断开连接
    print("---------------TD_OnFrontDisconnected---------------")


def TD_OnFrontConnectedFailer():
    # 连接失败
    print("---------------TD_OnFrontConnectedFailer---------------")


def TD_OnError():
    # 错误信息回报
    print("---------------TD_OnRspError---------------")
    data = cast(td.GetCmdContent_Error(), POINTER(VNCThostFtdcRspInfoField))
    print("ErrorID %s" % (str(data[0].ErrorID)))  # 错误代码
    print("ErrorMsg %s" % (str(data[0].ErrorMsg)))  # 错误信息


def TD_OnOrder():
    # 订单回报
    print("---------------TD_OnRspOrder---------------")
    data = cast(td.GetCmdContent_Order(), POINTER(VNCThostFtdcOrderField))
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("InvestorID %s" % (str(data[0].InvestorID)))
    print("InstrumentID %s" % (str(data[0].InstrumentID)))
    print("OrderRef %s" % (str(data[0].OrderRef)))
    print("UserID %s" % (str(data[0].UserID)))
    print("OrderPriceType %s" % (str(data[0].OrderPriceType)))
    print("Direction %s" % (str(data[0].Direction)))
    print("CombOffsetFlag %s" % (str(data[0].CombOffsetFlag)))
    print("CombHedgeFlag %s" % (str(data[0].CombHedgeFlag)))
    print("LimitPrice %s" % (str(data[0].LimitPrice)))
    print("VolumeTotalOriginal %s" % (str(data[0].VolumeTotalOriginal)))
    print("TimeCondition %s" % (str(data[0].TimeCondition)))
    print("GTDDate %s" % (str(data[0].GTDDate)))
    print("MinVolume %s" % (str(data[0].MinVolume)))
    print("ContingentCondition %s" % (str(data[0].ContingentCondition)))
    print("StopPrice %s" % (str(data[0].StopPrice)))
    print("ForceCloseReason %s" % (str(data[0].ForceCloseReason)))
    print("IsAutoSuspend %s" % (str(data[0].IsAutoSuspend)))
    print("BusinessUnit %s" % (str(data[0].BusinessUnit)))
    print("RequestID %s" % (str(data[0].RequestID)))
    print("OrderLocalID %s" % (str(data[0].OrderLocalID)))
    print("ExchangeID %s" % (str(data[0].ExchangeID)))
    print("ParticipantID %s" % (str(data[0].ParticipantID)))
    print("ClientID %s" % (str(data[0].ClientID)))
    print("ExchangeInstID %s" % (str(data[0].ExchangeInstID)))
    print("TraderID %s" % (str(data[0].TraderID)))
    print("InstallID %s" % (str(data[0].InstallID)))
    print("OrderSubmitStatus %s" % (str(data[0].OrderSubmitStatus)))
    print("NotifySequence %s" % (str(data[0].NotifySequence)))
    print("TradingDay %s" % (str(data[0].TradingDay)))
    print("SettlementID %s" % (str(data[0].SettlementID)))
    print("OrderSysID %s" % (str(data[0].OrderSysID)))
    print("OrderSource %s" % (str(data[0].OrderSource)))
    print("OrderStatus %s" % (str(data[0].OrderStatus)))
    print("OrderType %s" % (str(data[0].OrderType)))
    print("VolumeTraded %s" % (str(data[0].VolumeTraded)))
    print("VolumeTotal %s" % (str(data[0].VolumeTotal)))


def TD_OnSettlementInfoConfirm():
    # 结算确认回报
    print("---------------TD_OnRspSettlementInfoConfirm---------------")
    data = cast(td.GetCmdContent_Settlement(), POINTER(VNCThostFtdcSettlementInfoConfirmField))
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("InvestorID %s" % (str(data[0].InvestorID)))  # 投资者代码
    print("ConfirmDate %s" % (str(data[0].ConfirmDate)))  # 确认日期
    print("ConfirmTime %s" % (str(data[0].ConfirmTime)))  # 确认时间


def TD_OnMaxOrderVolume():
    # 查询最大报单数量响应
    print("---------------TD_OnRspSettlementInfoConfirm---------------")
    data = cast(td.GetCmdContent_Settlement(), POINTER(VNCThostFtdcQueryMaxOrderVolumeField))
    print("BrokerID %s" % (str(data[0].BrokerID)))  # 经纪公司代码
    print("InvestorID %s" % (str(data[0].InvestorID)))  # 投资者代码
    print("InstrumentID %s" % (str(data[0].InstrumentID)))  # 合约代码
    print("Direction %s" % (str(data[0].Direction)))  # 买卖方向
    print("OffsetFlag %s" % (str(data[0].OffsetFlag)))  # 开平标志
    print("HedgeFlag %s" % (str(data[0].HedgeFlag)))  # 投机套保标志
    print("MaxVolume %s" % (str(data[0].MaxVolume)))  # 最大允许报单数量

tddict = {
    TD_EMPTY: TD_OnEmptyCmd,
    TD_LOGIN_SCUESS: TD_OnUserLogin,
    TD_LOGIN_DENIED: TD_OnUserLoginDenied,
    TD_LOGIN_ERRORPASSWORD: TD_OnUserLoginErrPassword,
    TD_LOGINOUT_SCUESS: TD_OnUserLogout,
    TD_NETCONNECT_SCUESS: TD_OnFrontConnected,
    TD_NETCONNECT_BREAK: TD_OnFrontDisconnected,
    TD_NETCONNECT_FAILER: TD_OnFrontConnectedFailer,
    TD_ERROR: TD_OnError,
    TD_ORDER_INFO: TD_OnOrder,
    TD_SETTLEMENTINFOCONFIRM: TD_OnSettlementInfoConfirm,
    TD_MAXORDERVOLUME: TD_OnMaxOrderVolume
}


# ------------------------------------------TD回调函数、相关变量结束----------------------------------------------
# main()为程序入口函数，所有的行情、交易订阅、指标调用、下单的逻辑均写在此函数内执行
def TDThread(func):
    print(u"官方微信公众号从网址 http://www.vnpy.cn 获取")

    retLogin = td.ReqUserLogin()  # 调用交易接口元素，通过 “ 接口变量.元素（接口类内部定义的方法或变量） ” 形式调用
    # Login()，不需要参数，Login读取QuickLibTD.ini的配置信息，并登录
    # 返回0， 表示登录成功，
    # 返回1， FutureTDAccount.ini错误
    # 返回2， 登录超时
    print('login: ', retLogin)
    if retLogin == 0:
        print('登陆交易成功')
    else:
        print('登陆交易失败')

    # 持仓数据在后台更新时，参数True为显示持仓状态，False为不显示持仓状态（仅对控制台有效）
    td.SetShowPosition(True)
    # 注意simnow模拟盘的交易服务器不稳定，经常会出现查询不到的情况。实盘账户绑定的交易服务器无此问题。

    while (1):  # 死循环，反复执行
        print("Wait for a New Cmd");
        # 消息驱动，包括了各种回调，在没有消息到来时，一直处于阻塞状态，不占用CPU。当回调函数内有大量耗时的CPU计算时，建议回调函数内采用多进程或进程池来进行计算处理，以免阻塞消息驱动线程。
        tddict[td.OnCmd()]()
        print("Get A New cmd");
        OrderRef2 = td.InsertOrder('ag2403', VN_D_Buy, VN_OF_Open, VN_OPT_LimitPrice, 22450, 1)
        # 如果值为-999999999（初始值），则表示尚未获得数据
        '''
        print (u'(1)动态权益：%0.02f'%td.QryBalance(True))
        print (u'(2)静态权益：%0.02f'%td.QryBalance(False))      
        print (u'(3)可用资金：%0.02f'%td.QryAvailable())
        print (u'(4)zn1701今日空单持仓：%d'%td.QryPosition('rb1701',VN_POSITION_Sell_Today))   
        print (u'(5)zn1701今日多单持仓：%d'%td.QryPosition('rb1701',VN_POSITION_Buy_Today))   
        print (u'(6)zn1701非今日空单持仓：%d'%td.QryPosition('rb1701',VN_POSITION_Sell_History))   
        print (u'(7)zn1701非今日多单持仓：%d'%td.QryPosition('rb1701',VN_POSITION_Buy_History))   
        print (u'(8)zn1701空单持仓总计：%d'%td.QryPosition('rb1701',VN_POSITION_Sell_All))   
        print (u'(9)zn1701多单持仓总计：%d'%td.QryPosition('rb1701',VN_POSITION_Buy_All))   
        '''
        # print '--------------------------------------------------------'
        # time.sleep(3)  #sleep1秒，防止死循环导致CPU占有率过高，1即可，不宜过大，若过大会导致程序进程长时间无响应，丢失行情数据


threads = []
thread1 = threading.Thread(target=MDThread, args=(u'a',))
threads.append(thread1)
thread2 = threading.Thread(target=TDThread, args=(u'a',))
threads.append(thread2)


# main()为程序入口函数，所有的行情、交易订阅、指标调用、下单的逻辑均写在此函数内执行
def main():
    print("微信公众号从官方首页获得 https://www.vnpy.cn")

    for tid in threads:
        tid.setDaemon(True)
        tid.start()
        # tid.join()   #子线程结束后才能结束主线程main，由于子线程有while循环，所以永远不会结束主线程
    while (1):
        time.sleep(100)


if __name__ == '__main__':
    main()
